package service

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/kelvins-io/common/json"
	"github.com/etcd-io/etcd/client"
	"os"
	"strings"
	"time"
)

func newEtcd(urls string) (client.Client, error) {
	cli, err := client.New(client.Config{
		Endpoints:               strings.Split(urls, ","),
		Transport:               client.DefaultTransport,
		HeaderTimeoutPerRequest: 10 * time.Second,
	})
	if err != nil {
		return nil, err
	}

	return cli, nil
}

type serviceLB struct {
	EtcdServerUrl string
	ServerName    string
}

func newServiceLB(etcdServerUrl, serverName string) *serviceLB {
	return &serviceLB{EtcdServerUrl: etcdServerUrl, ServerName: serverName}
}

const (
	EnvEtcdv3ServerUrl  = "ETCDV3_SERVER_URL"
	EnvEtcdv3ServerUrls = "ETCDV3_SERVER_URLS"
)

func getEtcdV3ServerURL() string {
	return os.Getenv(EnvEtcdv3ServerUrl)
}

func getEtcdV3ServerURLs() string {
	values := os.Getenv(EnvEtcdv3ServerUrls)
	if values != "" {
		return values
	}

	return getEtcdV3ServerURL()
}

const (
	Service        = "/kelvins-service"
	DefaultCluster = "cluster"
)

var ErrServiceConfigKeyNotExist = errors.New("service config key not exist")

type serviceConfigClient struct {
	ServiceLB *serviceLB
	serviceConfig
}

type serviceConfig struct {
	ServiceVersion string `json:"service_version"`
	ServicePort    string `json:"service_port"`
	ServiceIP      string `json:"service_ip"`
	ServiceKind    string `json:"service_kind"`
	LastModified   string `json:"last_modified"`
}

func newServiceConfigClient(slb *serviceLB) *serviceConfigClient {
	return &serviceConfigClient{ServiceLB: slb}
}

// GetKeyName etcd key cannot end with a number
func (s *serviceConfigClient) GetKeyName(serverName string, sequences ...string) string {
	key := Service + "." + serverName + "." + DefaultCluster
	for _, s := range sequences {
		key += "/" + s
	}
	return key
}

func (s *serviceConfigClient) GetConfig(sequence string) (*serviceConfig, error) {
	cli, err := newEtcd(s.ServiceLB.EtcdServerUrl)
	if err != nil {
		return nil, fmt.Errorf("util.NewEtcd err: %v，etcdUrl: %v", err, s.ServiceLB.EtcdServerUrl)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	key := s.GetKeyName(s.ServiceLB.ServerName, sequence)
	serviceInfo, err := client.NewKeysAPI(cli).Get(ctx, key, nil)
	if err != nil {
		if client.IsKeyNotFound(err) {
			return nil, ErrServiceConfigKeyNotExist
		}
		return nil, fmt.Errorf("cli.Get err: %v, key: %v", err, key)
	}

	var config serviceConfig
	if len(serviceInfo.Node.Value) > 0 {
		err = json.Unmarshal(serviceInfo.Node.Value, &config)
		if err != nil {
			return nil, fmt.Errorf("json.Unmarshal err: %v, key: %v,values: %v", err, key, serviceInfo.Node.Value)
		}
	}

	if config.ServicePort == "" {
		return nil, fmt.Errorf("servicePort is empty, key: %s", key)
	}

	return &config, nil
}

func (s *serviceConfigClient) ClearConfig(sequence string) error {
	cli, err := newEtcd(s.ServiceLB.EtcdServerUrl)
	if err != nil {
		return fmt.Errorf("util.NewEtcd err: %v，etcdUrl: %v", err, s.ServiceLB.EtcdServerUrl)
	}

	var key string
	if sequence != "" {
		key = s.GetKeyName(s.ServiceLB.ServerName, sequence)
	}
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	var delOptions *client.DeleteOptions
	if sequence == "" {
		delOptions = &client.DeleteOptions{
			PrevValue: "",
			PrevIndex: 0,
			Recursive: true,
			Dir:       true,
		}
	}
	_, err = client.NewKeysAPI(cli).Delete(ctx, key, delOptions)
	if err != nil {
		if client.IsKeyNotFound(err) {
			return ErrServiceConfigKeyNotExist
		}
		return fmt.Errorf("cli.Delete err: %v key: %v", err, key)
	}

	return nil
}

func (s *serviceConfigClient) WriteConfig(sequence string, c serviceConfig) error {
	cli, err := newEtcd(s.ServiceLB.EtcdServerUrl)
	if err != nil {
		return fmt.Errorf("util.NewEtcd err: %v，etcdUrl: %v", err, s.ServiceLB.EtcdServerUrl)
	}

	key := s.GetKeyName(s.ServiceLB.ServerName, sequence)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	jsonConfig, err := json.MarshalToString(&c)
	if err != nil {
		return fmt.Errorf("json.MarshalToString err: %v key: %v config: %+v", err, key, c)
	}
	_, err = client.NewKeysAPI(cli).Set(ctx, key, jsonConfig, &client.SetOptions{
		PrevExist: client.PrevNoExist,
	})
	if err != nil {
		return fmt.Errorf("cli.Set err: %v key: %v values: %v", err, key, jsonConfig)
	}

	return nil
}

func (s *serviceConfigClient) ListConfigs() (map[string]*serviceConfig, error) {
	return s.listConfigs("/")
}

func (s *serviceConfigClient) GetConfigs() (map[string]*serviceConfig, error) {
	return s.listConfigs(s.GetKeyName(s.ServiceLB.ServerName))
}

func (s *serviceConfigClient) Watch(ctx context.Context) (<-chan struct{}, error) {
	notice := make(chan struct{}, 1)
	cli, err := newEtcd(s.ServiceLB.EtcdServerUrl)
	if err != nil {
		return notice, fmt.Errorf("util.NewEtcd err: %v，etcdUrl: %v", err, s.ServiceLB.EtcdServerUrl)
	}

	kapi := client.NewKeysAPI(cli)
	ctx, cancel := context.WithCancel(ctx)
	watcher := kapi.Watcher(s.GetKeyName(s.ServiceLB.ServerName), &client.WatcherOptions{
		AfterIndex: 0,
		Recursive:  true,
	})
	go func() {
		defer func() {
			cancel()
			close(notice)
		}()
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			resp, err := watcher.Next(ctx)
			if err != nil {
				time.Sleep(500 * time.Millisecond)
				continue
			}

			if strings.ToLower(resp.Action) != "get" {
				// 防止notice来不及被客户端消费
				select {
				case <-notice:
				default:
				}
				notice <- struct{}{}
			}
		}
	}()

	return notice, nil
}

func (s *serviceConfigClient) listConfigs(key string) (map[string]*serviceConfig, error) {
	cli, err := newEtcd(s.ServiceLB.EtcdServerUrl)
	if err != nil {
		return nil, fmt.Errorf("util.NewEtcd err: %v，etcdUrl: %v", err, s.ServiceLB.EtcdServerUrl)
	}

	kapi := client.NewKeysAPI(cli)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	serviceInfos, err := kapi.Get(ctx, key, nil)
	if err != nil {
		if client.IsKeyNotFound(err) {
			return nil, ErrServiceConfigKeyNotExist
		}
		return nil, fmt.Errorf("cli.Get err: %v key: %v", err, key)
	}

	configs := make(map[string]*serviceConfig)
	for _, info := range serviceInfos.Node.Nodes {
		if len(info.Value) > 0 {
			index := strings.Index(info.Key, Service)
			if index == 0 {
				config := &serviceConfig{}
				err := json.Unmarshal(info.Value, config)
				if err != nil {
					return nil, fmt.Errorf("json.UnmarshalByte err: %v values: %v", err, info.Value)
				}

				configs[info.Key] = config
			}
		}
	}

	return configs, nil
}
